<?php

declare(strict_types=1);

namespace app\service\user;

use app\model\Org;
use app\model\OrgAddress;
use app\model\OrgContact;
use app\model\OrgMember;
use app\model\User;
use app\model\UserAuth;
use app\model\UserProfile;
use InvalidArgumentException;
use think\Collection;
use think\facade\Db;

class ProfileService
{
    public function getProfile(User $user): array
    {
        $user = $this->reloadUser($user);

        return [
            'profile' => $this->formatUserProfile($user),
            'roles' => $this->extractRoles($user),
            'identities' => $this->extractIdentities($user),
            'orgs' => $this->formatOwnerOrganizations($user),
        ];
    }

    public function updateProfile(User $user, array $payload): User
    {
        $email = array_key_exists('email', $payload) ? trim((string) $payload['email']) : null;
        $nickname = array_key_exists('nickname', $payload) ? (string) $payload['nickname'] : null;
        $realName = array_key_exists('real_name', $payload) ? (string) $payload['real_name'] : null;
        $avatar = array_key_exists('avatar', $payload) ? trim((string) $payload['avatar']) : null;

        if ($email !== null) {
            $this->assertEmailUnique($email, (int) $user->id);
        }

        Db::transaction(function () use ($user, $email, $nickname, $realName, $avatar) {
            $updates = [];
            if ($email !== null) {
                $updates['email'] = $email !== '' ? $email : null;
            }

            if ($updates !== []) {
                $user->save($updates);
            }

            if ($nickname !== null || $realName !== null || $avatar !== null) {
                $this->saveUserProfile($user, [
                    'nickname' => $nickname,
                    'real_name' => $realName,
                    'avatar' => $avatar,
                ]);
            }
        });

        return $this->reloadUser($user);
    }

    public function updatePassword(User $user, string $currentPassword, string $newPassword): bool
    {
        if ($currentPassword === '' || !password_verify($currentPassword, (string) $user->password_hash)) {
            throw new InvalidArgumentException('当前密码不正确');
        }

        $passwordHash = password_hash($newPassword, PASSWORD_BCRYPT);

        return (bool) Db::transaction(function () use ($user, $passwordHash) {
            $user->save(['password_hash' => $passwordHash]);
            $this->syncPasswordAuthRecord($user, $passwordHash);

            return true;
        });
    }

    public function updateOrgSettings(User $user, int $orgId, array $payload): array
    {
        /** @var OrgMember|null $member */
        $member = OrgMember::with(['org.contact', 'org.addresses'])
            ->where('org_id', $orgId)
            ->where('user_id', $user->id)
            ->where('member_role', OrgMember::ROLE_OWNER)
            ->where('status', 1)
            ->find();

        if (!$member || !$member->org) {
            throw new InvalidArgumentException('无权操作该组织或组织不存在');
        }

        $contactPayload = array_key_exists('contact', $payload) ? $this->prepareContactPayload($payload['contact']) : null;
        $shippingPayload = array_key_exists('shipping_address', $payload)
            ? $this->prepareAddressPayload($payload['shipping_address'], 'shipping')
            : null;
        $receivingPayload = array_key_exists('receiving_address', $payload)
            ? $this->prepareAddressPayload($payload['receiving_address'], 'receiving')
            : null;

        Db::transaction(function () use ($member, $contactPayload, $shippingPayload, $receivingPayload) {
            $org = $member->org;
            if (!$org) {
                throw new InvalidArgumentException('组织不存在');
            }

            if ($contactPayload !== null) {
                $this->saveOrgContact($org, $contactPayload);
            }
            if ($shippingPayload !== null) {
                $this->saveOrgAddress($org, $shippingPayload);
            }
            if ($receivingPayload !== null) {
                $this->saveOrgAddress($org, $receivingPayload);
            }
        });

        /** @var OrgMember|null $fresh */
        $fresh = OrgMember::with(['org.contact', 'org.addresses'])
            ->find((int) $member->id);

        if (!$fresh || !$fresh->org) {
            throw new InvalidArgumentException('组织数据刷新失败');
        }

        $collection = new Collection([$fresh]);
        $formatted = $this->formatOwnerOrganizationsFromCollection($collection);

        return $formatted[0] ?? [];
    }

    protected function reloadUser(User $user): User
    {
        /** @var User|null $fresh */
        $fresh = User::with([
            'profile',
            'roles',
            'identities.org',
            'identities.region',
            'orgMembers.org.contact',
            'orgMembers.org.addresses',
        ])->find((int) $user->id);

        return $fresh ?? $user;
    }

    protected function formatUserProfile(User $user): array
    {
        $profile = $user->profile;

        return [
            'id' => (int) $user->id,
            'phone' => (string) $user->phone,
            'email' => $user->email,
            'status' => (int) $user->status,
            'nickname' => $profile->nickname ?? '',
            'real_name' => $profile->real_name ?? '',
            'avatar' => $profile->avatar ?? '',
        ];
    }

    protected function extractRoles(User $user): array
    {
        $roles = $user->roles instanceof Collection ? $user->roles : Collection::make($user->roles ?? []);

        return array_values(
            $roles->map(static function ($role) {
                return [
                    'id' => (int) ($role->id ?? 0),
                    'name' => $role->name ?? '',
                    'slug' => $role->slug ?? ($role->code ?? ''),
                ];
            })
                ->filter(static fn(array $role) => $role['id'] > 0 || $role['slug'] !== '')
                ->toArray()
        );
    }

    protected function extractIdentities(User $user): array
    {
        $identities = $user->identities instanceof Collection ? $user->identities : Collection::make($user->identities ?? []);

        return array_values(
            $identities->map(static function ($identity) {
                $org = $identity->org ?? null;
                $region = $identity->region ?? null;

                return [
                    'id' => (int) ($identity->id ?? 0),
                    'type' => (string) ($identity->identity_type ?? ''),
                    'status' => (int) ($identity->status ?? 0),
                    'org_id' => $identity->org_id ?? null,
                    'org_name' => $org?->name,
                    'region_id' => $identity->region_id ?? null,
                    'region_name' => $region?->name,
                    'verified_at' => $identity->verified_at ?? null,
                ];
            })->toArray()
        );
    }

    protected function formatOwnerOrganizations(User $user): array
    {
        $memberships = $this->loadOwnerMemberships($user);

        return $this->formatOwnerOrganizationsFromCollection($memberships);
    }

    /**
     * @return Collection<int, OrgMember>
     */
    protected function loadOwnerMemberships(User $user): Collection
    {
        return OrgMember::with(['org.contact', 'org.addresses'])
            ->where('user_id', $user->id)
            ->where('member_role', OrgMember::ROLE_OWNER)
            ->where('status', 1)
            ->select();
    }

    /**
     * @param Collection<int, OrgMember> $memberships
     */
    protected function formatOwnerOrganizationsFromCollection(Collection $memberships): array
    {
        return array_values(
            $memberships->map(function (OrgMember $member) {
                $org = $member->org;
                if (!$org) {
                    return null;
                }

                $contact = $org->contact ? $this->formatContact($org->contact->toArray()) : $this->defaultContact();
                $addresses = $org->addresses instanceof Collection
                    ? $org->addresses
                    : Collection::make($org->addresses ?? []);

                $shipping = $this->extractAddress($addresses, 'shipping');
                $receiving = $this->extractAddress($addresses, 'receiving');
                if ($receiving === null) {
                    $receiving = $this->extractAddress($addresses, 'billing');
                }

                return [
                    'id' => (int) $org->id,
                    'name' => $org->name,
                    'org_type' => $org->org_type,
                    'status' => (int) $org->status,
                    'member_role' => $member->member_role,
                    'contact' => $contact,
                    'shipping_address' => $shipping ?? $this->defaultAddress('shipping'),
                    'receiving_address' => $receiving ?? $this->defaultAddress('receiving'),
                ];
            })
                ->filter()
                ->toArray()
        );
    }

    protected function saveUserProfile(User $user, array $payload): void
    {
        $profile = $user->profile;
        $data = [];

        if (array_key_exists('nickname', $payload)) {
            $value = trim((string) ($payload['nickname'] ?? ''));
            $data['nickname'] = $value;
        }

        if (array_key_exists('real_name', $payload)) {
            $value = trim((string) ($payload['real_name'] ?? ''));
            $data['real_name'] = $value;
        }

        if (array_key_exists('avatar', $payload)) {
            $value = (string) ($payload['avatar'] ?? '');
            $data['avatar'] = $value;
        }

        if ($data === []) {
            return;
        }

        if ($profile) {
            $profile->save($data);
            return;
        }

        $data['user_id'] = $user->id;
        if (!isset($data['nickname'])) {
            $data['nickname'] = $user->phone ? mb_substr((string) $user->phone, -4) : '';
        }

        UserProfile::create($data);
    }

    protected function syncPasswordAuthRecord(User $user, string $passwordHash): void
    {
        $record = UserAuth::withTrashed()
            ->where('user_id', $user->id)
            ->where('identity_type', UserAuth::TYPE_PASSWORD)
            ->find();

        if ($record) {
            $record->credential = $passwordHash;
            if ($record->identifier !== $user->phone) {
                $record->identifier = (string) $user->phone;
            }
            if ($record->delete_time) {
                $record->restore();
            }
            $record->status = 1;
            $record->save();
            return;
        }

        UserAuth::create([
            'user_id' => $user->id,
            'identity_type' => UserAuth::TYPE_PASSWORD,
            'identifier' => (string) $user->phone,
            'credential' => $passwordHash,
            'status' => 1,
        ]);
    }

    protected function prepareContactPayload(mixed $contact): array
    {
        if ($contact === null) {
            return [];
        }

        if (!is_array($contact)) {
            throw new InvalidArgumentException('联系信息格式不正确');
        }

        $allowed = [
            'contact_name',
            'contact_phone',
            'contact_email',
            'bank_name',
            'bank_account',
            'bank_account_name',
            'invoice_title',
            'invoice_taxno',
        ];

        $data = [];
        foreach ($allowed as $field) {
            if (array_key_exists($field, $contact)) {
                $data[$field] = trim((string) $contact[$field]);
            }
        }

        return $data;
    }

    protected function prepareAddressPayload(mixed $address, string $type): array
    {
        if ($address === null) {
            return [];
        }

        if (!is_array($address)) {
            throw new InvalidArgumentException('地址信息格式不正确');
        }

        $data = [
            'address_type' => $type,
            'contact_name' => trim((string) ($address['contact_name'] ?? '')),
            'contact_phone' => trim((string) ($address['contact_phone'] ?? '')),
            'province' => trim((string) ($address['province'] ?? '')),
            'city' => trim((string) ($address['city'] ?? '')),
            'district' => trim((string) ($address['district'] ?? '')),
            'address' => trim((string) ($address['address'] ?? '')),
            'postal_code' => trim((string) ($address['postal_code'] ?? '')),
            'is_default' => (int) (!empty($address['is_default'])),
        ];

        return $data;
    }

    protected function saveOrgContact(Org $org, array $payload): void
    {
        $record = $org->contact ?: OrgContact::where('org_id', $org->id)->find();
        $payload['org_id'] = $org->id;

        if ($record) {
            $record->save($payload);
            return;
        }

        OrgContact::create($payload);
    }

    protected function saveOrgAddress(Org $org, array $payload): void
    {
        $type = $payload['address_type'] ?? 'shipping';
        /** @var OrgAddress|null $record */
        $record = OrgAddress::withTrashed()
            ->where('org_id', $org->id)
            ->where('address_type', $type)
            ->order('is_default', 'desc')
            ->find();

        $payload['org_id'] = $org->id;
        $payload['address_type'] = $type;

        if ($record) {
            if ($record->delete_time) {
                $record->restore();
            }
            $record->save($payload);
            return;
        }

        OrgAddress::create($payload);
    }

    protected function extractAddress(Collection $addresses, string $type): ?array
    {
        $record = $addresses->first(static function ($address) use ($type) {
            if ($address instanceof OrgAddress) {
                return $address->address_type === $type;
            }
            if (is_array($address)) {
                return ($address['address_type'] ?? null) === $type;
            }

            return false;
        });

        if (!$record) {
            return null;
        }

        $data = $record instanceof OrgAddress ? $record->toArray() : (array) $record;
        return $this->formatAddress($data, $type);
    }

    protected function formatContact(array $contact): array
    {
        $defaults = $this->defaultContact();

        foreach ($defaults as $key => $value) {
            if (array_key_exists($key, $contact)) {
                $defaults[$key] = $contact[$key] ?? '';
            }
        }

        return $defaults;
    }

    protected function formatAddress(array $address, string $type): array
    {
        $defaults = $this->defaultAddress($type);

        foreach ($defaults as $key => $value) {
            if (array_key_exists($key, $address)) {
                $defaults[$key] = $address[$key];
            }
        }

        $defaults['address_type'] = $type;

        return $defaults;
    }

    protected function defaultContact(): array
    {
        return [
            'contact_name' => '',
            'contact_phone' => '',
            'contact_email' => '',
            'bank_name' => '',
            'bank_account' => '',
            'bank_account_name' => '',
            'invoice_title' => '',
            'invoice_taxno' => '',
        ];
    }

    protected function defaultAddress(string $type): array
    {
        return [
            'id' => null,
            'contact_name' => '',
            'contact_phone' => '',
            'province' => '',
            'city' => '',
            'district' => '',
            'address' => '',
            'postal_code' => '',
            'is_default' => 1,
            'address_type' => $type,
        ];
    }

    protected function assertEmailUnique(string $email, int $userId): void
    {
        if ($email === '') {
            return;
        }

        $query = User::where('email', $email);
        if ($userId > 0) {
            $query->where('id', '<>', $userId);
        }

        if ($query->count() > 0) {
            throw new InvalidArgumentException('邮箱已存在');
        }
    }
}
